#include "main.h"
#include "co_tests.h"
#include "evq_tests.h"
#include "comp_tests.h"

SUITE(comp);

// component state
static volatile struct
{
    bool running;       // the component is active
    int call_counter;   // number of times any call has been made
    int value_counter;  // the value adjusted by incr/decr
    int signal_counter; // number of times a blind signal has been sent
    int yield_counter;  // number of times in a yield loop
} test_1_state;

// message formats

typedef struct test_1_message
{
    enum
    {
        T1_INVALID,

        T1_INITIALIZE,
        T1_INCREMENT,
        T1_DECREMENT,
        T1_YIELD,
        T1_TERMINATE,
        T1_CALL,

        T1_NUM_MESSAGES,
    } type;
    union
    {
        void *initialize_data;
        int   call_data;
    } data;
} test_1_message;

// mandatory components
COMP_DEFINE(TEST_1, STACK_SIZE);

static void TEST_1_pump_x_init(void *data)
{
    UNUSED(data);

    test_1_state.running = true;
}

static comp_body_t TEST_1_pump_x_body(void *data)
{
    comp_body_t rv = COMP_CONTINUE;
    test_1_message *msg = data;

    if (msg == NULL)
    {
        // this is a self-signal
        test_1_state.signal_counter++;
    }
    else
    {
        test_1_state.call_counter++;

        switch (msg->type)
        {
        case T1_TERMINATE:
            rv = COMP_TERMINATE;
            break;

        case T1_INCREMENT:
            test_1_state.value_counter++;
            break;

        case T1_DECREMENT:
            test_1_state.value_counter--;
            break;

        case T1_YIELD:
            for (int i = 0; i < 5; i++)
            {
                test_1_state.yield_counter++;
                TEST_1_yield();
            }
            break;

        case T1_CALL:
            {
                int num = msg->data.call_data;
                msg->data.call_data = num * num;
            }
            break;

        default:
            rv = COMP_TERMINATE;
            break;
        }
    }

    return rv;
}

static void TEST_1_pump_x_terminate(void *data)
{
    UNUSED(data);

    test_1_state.running = false;
}

// API
int t1_call(int num)
{
    static test_1_message call_msg =
    {
        .type = T1_CALL,
    };
    call_msg.data.call_data = num;
    TEST_1_call(&call_msg);
    return call_msg.data.call_data;
}

void t1_incr(void)
{
    static test_1_message incr_msg =
    {
        .type = T1_INCREMENT,
    };
    TEST_1_send(&incr_msg);
}

void t1_decr(void)
{
    static test_1_message decr_msg =
    {
        .type = T1_DECREMENT,
    };
    TEST_1_send(&decr_msg);
}

void t1_signal(void)
{
    TEST_1_signal();
}

void t1_yield(void)
{
    static test_1_message yield_msg =
    {
        .type = T1_YIELD,
    };
    TEST_1_send(&yield_msg);
}

//**************************************
// TEST 1
//**************************************
evq_queue_t master_queue;

TEST comp_test_1(void)
{
    static test_1_message init_msg =
    {
        .type                 = T1_INITIALIZE,
        .data.initialize_data = NULL,
    };
    ASSERT_EQm("component coroutine is null", TEST_1_co, NULL);
    ASSERT_EQm("component queue is null", TEST_1_queue, NULL);
    ASSERT_EQm("master queue is null", master_queue, NULL);

    master_queue = evq_create_queue();
    ASSERT_NEQm("master queue is not null", master_queue, NULL);

    TEST_1_init(master_queue, &init_msg);
    ASSERT_NEQm("component coroutine is not null", TEST_1_co, NULL);
    ASSERT_NEQm("component queue is not null", TEST_1_queue, NULL);
    ASSERT_EQm("master queue is stored in component", TEST_1_master_queue, master_queue);
    ASSERTm("component is in running state", test_1_state.running);

    PASSm("initialize and instantiate a standard component");
}

//**************************************
// TEST 2
//**************************************

TEST comp_test_2(void)
{
    t1_incr(); t1_incr(); t1_incr();
    t1_decr(); t1_decr(); t1_decr(); t1_decr();
    t1_incr(); t1_incr(); t1_incr(); t1_incr(); t1_incr(); t1_incr();
    evq_process_one_tick(master_queue, NULL);
    ASSERT_EQm("increments and decrements come to correct total", test_1_state.value_counter, 5);

    PASSm("call simple operations via events");
}

//**************************************
// TEST 3
//**************************************

TEST comp_test_3(void)
{
    t1_signal();
    evq_process_one_tick(master_queue, NULL);
    ASSERT_EQm("signal received", test_1_state.signal_counter, 1);
    t1_signal();
    evq_process_one_tick(master_queue, NULL);
    ASSERT_EQm("signal received", test_1_state.signal_counter, 2);

    PASSm("simple self-signal operation");
}

//**************************************
// TEST 4
//**************************************

TEST comp_test_4(void)
{
    t1_yield();
    evq_process_for(master_queue, 15, NULL);
    ASSERT_NEQm("signal received", test_1_state.signal_counter, 0);

    PASSm("");
}

//**************************************
// TEST 5
//**************************************

TEST comp_test_5(void)
{
    const tick_ms before = evq_get_tick();
    TEST_1_delay(15);
    ASSERT_EQm("delay is the right amount", (evq_get_tick() - before), 15);
    TEST_1_delay(10);
    ASSERT_EQm("delay is the right amount", (evq_get_tick() - before), 25);

    PASSm("delay check");
}

//**************************************
// TEST 6
//**************************************

COMP_DECLARE(TEST_2);

COMP_DEFINE(TEST_2, STACK_SIZE);
static volatile struct
{
    bool init;
    bool running;
    int counter;
} test_2_state;

static void TEST_2_pump_x_init(void *data)
{
    UNUSED(data);
    test_2_state.counter = 0;
    test_2_state.running = true;
    test_2_state.init = false;
    TEST_2_signal();
}
static comp_body_t TEST_2_pump_x_body(void *data)
{
    comp_body_t rv = COMP_CONTINUE;
    UNUSED(data);

    if (++test_2_state.counter >= 5)
    {
        rv = COMP_TERMINATE;
    }

    TEST_2_delay(5);
    TEST_2_signal();
    return rv;
}
static void TEST_2_pump_x_terminate(void *data)
{
    UNUSED(data);
    test_2_state.running = false;
}

TEST comp_test_6(void)
{
    test_2_state.init = true;
    TEST_2_init(master_queue, NULL);
    evq_process_for(master_queue, 50, NULL);
    ASSERT_FALSEm("test state is no longer initialized", test_2_state.init);
    ASSERT_FALSEm("test is not running", test_2_state.running);
    ASSERT_EQm("test ran for assigned count", test_2_state.counter, 5);

    PASSm("delay/signal check");
}

//**************************************
// TEST 7
//**************************************

TEST comp_test_7(void)
{
    int val = t1_call(6);
    ASSERT_EQm("the square of 6 is 36", val, 36);
    val = t1_call(11);
    ASSERT_EQm("the square of 11 is 121", val, 121);

    PASSm("delay check");
}

SUITE(comp)
{
    RUN_TEST(comp_test_1);
    RUN_TEST(comp_test_2);
    RUN_TEST(comp_test_3);
    RUN_TEST(comp_test_4);
    RUN_TEST(comp_test_5);
    RUN_TEST(comp_test_6);
    RUN_TEST(comp_test_7);
}
